<!DOCTYPE html>
<html>
    <head>
        <title>Dynamo Profiler</title>
        <style type="text/css">
            body {
                background: #fff;
                font-family: Arial, Helvetica, Helvetica Neue, Verdana, sans-serif;
                font-size: 12px;
                line-height: 18px;
            }

            table {
                font-family: Arial, Helvetica, Helvetica Neue, Verdana, sans-serif;
                font-size: 12px;
                line-height: 18px;
            }

            table td {
                vertical-align: top;
            }

            #frames {
                width: 1000px;
                border-color: rgb(100, 100, 100);
                border-width: 1px;
                border-style: solid;
            }

            #frame {
                width: 1000px;
                height: 1000px;
                border-color: #888;
                border-width: 1px;
                border-style: solid;
            }

            #plot {
                width: 1000px;
                height: 400px;
                border-color: #888;
                border-width: 1px;
                border-style: solid;
                padding: 16px;
            }

            div.frameblock_green {
                background: -ms-linear-gradient(top, hsl(130, 60%, 50%) 50%, hsl(130, 20%, 50%) 100%);
                background: -webkit-linear-gradient(top, hsl(130, 60%, 50%) 50%, hsl(130, 20%, 50%) 100%);
                float: left;
                border-color: rgb(100,100,100);
                border-style: solid;
                border-width: 1px;
                margin: -1px;
                padding: 0;
            }

            div.frameblock_red {
                background: -ms-linear-gradient(top, hsl(0, 60%, 50%) 50%, hsl(0, 20%, 50%) 100%);
                background: -webkit-linear-gradient(top, hsl(0, 60%, 50%) 50%, hsl(0, 20%, 50%) 100%);
                float: left;
                border-color: rgb(100,100,100);
                border-style: solid;
                border-width: 1px;
                margin: -1px;
                padding: 0;
            }

            div.square {
                width: 16px;
                height: 16px;
                float: left;
                margin-right: 4px;
            }

            table.prof-table {
              border: 1px;
              border-width: 1px;
              border-color: #888;
              border-style: solid;
              border-spacing: 0px;
              border-collapse:collapse;
              color: #333;
            }

            th.prof-table {
              text-align: left;
              border-width: 1px;
              border-color: #888;
              border-style: solid;
              padding: 4px;
            }

            td.prof-table {
              padding: 4px;
              border-width: 1px;
              border-color: #888;
              border-style: solid;
            }

            td.first {
              _width: 120px;
              text-align: left;
              _font-weight: bold;
            }

            td.second {
              width: 90px;
              text-align: left;
            }

            td.even {
              background-color: #fff;
            }

            td.odd {
              background-color: rgb(233, 233, 233);
            }

            /* Resource tab specifics */

            #tab_resources table > thead {
                background-color: #aaaaaa;
            }

            #tab_resources td, #tab_resources th {
                padding: 5px 10px;
            }

            #tab_resources td {
                border: solid 1px #aaa;
            }

            #tab_resources table {
                border-collapse: collapse;
                border: 2px solid #888;
            }

            #res_tables {
                margin-top: 16px;
            }

            .human-readable {
                white-space: pre;
                text-align: right;
            }

            /* Collection table tree specifics */

            #collection_table {
                float: left;
                margin-right: 16px;
                margin-bottom: 16px;
            }


            #collection_table .leaf {
                color: #666;
            }

            #collection_table .leaf label {
                margin-left: 32px;
            }

            #collection_table .collapsed {
                display: none;
            }

            #collection_table input[type="checkbox"] {
                display: none;
            }

            #collection_table input[type="checkbox"] + label::before {
                content: "\25B8 ";
                display: inline-block;
                margin-right: 4px;
                padding-left: 16px;
                color: #444;
            }

            #collection_table input[type="checkbox"]:checked + label::before {
                content: "\25BE ";
            }

            #collection_table .go_bone {
                color: #bbbbbb;
                font-style: italic;
            }

            #collection_table .go_generated {
                color: #4444aa;
            }

            div.resource-table-sort {
                float: left;
                color: #777;
                user-select: none;
            }

            div.resource-table-sort-none {
                float: left;
                visibility: hidden;
                user-select: none;
            }

            div.resource-table-label {
                display: inline;
                user-select: none;
            }

        </style>
        <script type="text/javascript">

            var ticksPerSecond = 1.0;
            var stringTable = {};
            var framesCpu = [];
            var framesResources = [];
            var framesGameObjects = [];

            // If running another server change base_url to value below, eg when testing
            //var base_url = 'http://localhost:8001/'
            var base_url = '/'
            var requestCpu = new XMLHttpRequest();
            var requestGameObjects = new XMLHttpRequest();
            var requestResources = new XMLHttpRequest();

            var capturedFrameCount = 0;
            var capturedSamplesData = [];

            var scopeColors = {};
            var counterColors = {};

            var plotSamples = {};
            var plotCounters = {};

            var sortResourcesSettings = { id : "resources_size_label", descending : true };
            var resourceItems = [];
            var resourceSizeTotal = 0;
            var resourceSizeOnDiscTotal = 0;

            // Global vars to help with expand/collapse collection table functionality
            var goRootsList = [];
            var goList = {};

            // Keep track of current profiler tab, always start with CPU
            var currentTab = "tab_resources";

            function switchTab()
            {
                var profilerTabs = document.getElementsByName('tab_selection');

                var newTab = currentTab;
                for (var i = 0, length = profilerTabs.length; i < length; i++)
                {
                    if (profilerTabs[i].checked)
                    {
                        newTab = profilerTabs[i].value;
                        break;
                    }
                }

                var currentTabElem = document.getElementById(currentTab);
                var newTabElem = document.getElementById(newTab);
                currentTabElem.style.display = "none";
                newTabElem.style.display = "block";
                currentTab = newTab;

                capture();
            }

            function capture() {
                if (currentTab == "tab_resources") {
                    captureGameObjects();
                    captureResources();
                }
            }

            function captureGameObjects() {
                framesGameObjects = [];
                requestGameObjects.open('GET', base_url + 'gameobjects_data', true);
                requestGameObjects.overrideMimeType('text/plain; charset=x-user-defined');
                requestGameObjects.onreadystatechange = handlerGameObjects;
                requestGameObjects.send();
            }

            function captureResources() {
                framesResources = [];
                requestResources.open('GET', base_url + 'resources_data', true);
                requestResources.overrideMimeType('text/plain; charset=x-user-defined');
                requestResources.onreadystatechange = handlerResources;
                requestResources.send();
            }

            readPtr = function(data, offset, size) {
                // no support for pointer arithmetic here anyway, so just use the string.
                return data.substring(offset, offset + size);
            }

            function memFileCreate(data, size) {
                return {data: data, size: size, offset: 0}
            }

            function memFileEof(f) {
                return f.offset >= f.size;
            }

            function memFileTell(f) {
                return f.offset;
            }

            function memFileReadUInt16(f) {
                if (memFileEof(f)) {
                    return null;
                }
                var a1 = f.data.charCodeAt(f.offset + 1) & 0xff;
                var a2 = f.data.charCodeAt(f.offset + 0) & 0xff;
                f.offset += 2;
                return (a1 << 8) + a2;
            }

            function memFileReadUInt32(f) {
                if (memFileEof(f)) {
                    return null;
                }
                var a1 = f.data.charCodeAt(f.offset + 3) & 0xff;
                var a2 = f.data.charCodeAt(f.offset + 2) & 0xff;
                var a3 = f.data.charCodeAt(f.offset + 1) & 0xff;
                var a4 = f.data.charCodeAt(f.offset + 0) & 0xff;
                f.offset += 4;
                return (a1 << 24) + (a2 << 16) + (a3 << 8) + a4;
            }

            function memFileReadUInt64(f) {
                if (memFileEof(f)) {
                    return null;
                }
                var a1 = f.data.charCodeAt(f.offset + 7) & 0xff;
                var a2 = f.data.charCodeAt(f.offset + 6) & 0xff;
                var a3 = f.data.charCodeAt(f.offset + 5) & 0xff;
                var a4 = f.data.charCodeAt(f.offset + 4) & 0xff;
                var a5 = f.data.charCodeAt(f.offset + 3) & 0xff;
                var a6 = f.data.charCodeAt(f.offset + 2) & 0xff;
                var a7 = f.data.charCodeAt(f.offset + 1) & 0xff;
                var a8 = f.data.charCodeAt(f.offset + 0) & 0xff;
                f.offset += 8;
                return (a1 << 56) + (a2 << 48) + (a3 << 40) + (a4 << 32) + (a5 << 24) + (a6 << 16) + (a7 << 8) + a8;
            }

            function memFileReadString(f) {
                var size = memFileReadUInt16(f);
                if (memFileEof(f)) {
                    return null;
                }
                if (memFileEof(f)) {
                    return null;
                }
                var s = f.data.substring(f.offset, f.offset + size)
                f.offset += size;
                return s;
            }

            function resourceCreate(name, type, size, sizeOnDisc, refCount) {
                return {
                    name: name,
                    type: type,
                    size: size,
                    sizeOnDisc: sizeOnDisc,
                    refCount: refCount
                }
            }

            function gameObjectCreate(name, resource, type, index, parent) {
                return {
                    name: name,
                    resource: resource,
                    type: type,
                    index: index,
                    parent: parent
                }
            }

            function isStreamEnd(file) {
                var file_pos = memFileTell(file);
                var s = file.data.substring(file_pos+2, file_pos+6);
                return s == 'ENDD';
            }

            function loadProfile(d, table) {
                var file = memFileCreate(d, d.length);

                ticksPerSecond = memFileReadUInt32(file) / 1000.0; // NOTE: We use ms internally
                var frameTime = 0;

                var samples = [];
                while(!memFileEof(file))
                {
                    if (isStreamEnd(file))
                    {
                        break;
                    }

                    var offset = memFileTell(file)

                    var nameId      = memFileReadUInt64(file);
                    var scopeId     = memFileReadUInt64(file);
                    var start       = memFileReadUInt32(file);
                    var elapsed     = memFileReadUInt32(file);
                    var threadId    = memFileReadUInt16(file);

                    var name = table[nameId];
                    var scope_name = table[scopeId];

                    var s = {
                        scope_name: scope_name,
                        name: scope_name + "." + name,
                        start: start / ticksPerSecond,
                        elapsed: elapsed / ticksPerSecond
                    };
                    samples.push(s);

                    frameTime = Math.max(frameTime, elapsed / ticksPerSecond);
                }

                // Forward to next segment
                memFileReadString(file); // ENDD

                var scopes_data = [];
                while(!memFileEof(file))
                {
                    if (isStreamEnd(file))
                    {
                        break;
                    }

                    var nameId      = memFileReadUInt64(file);
                    var elapsed     = memFileReadUInt32(file);
                    var count       = memFileReadUInt32(file);

                    var name = table[nameId];
                    scopes_data[name] = {
                        elapsed: elapsed,
                        count: count
                    };
                }

                // Forward to next segment
                memFileReadString(file); // ENDD

                var counters_data = [];
                while(!memFileEof(file))
                {
                    if (isStreamEnd(file))
                    {
                        break;
                    }

                    var nameId  = memFileReadUInt64(file);
                    var value   = memFileReadUInt32(file);

                    var name = table[nameId];
                    counters_data[name] = {
                        value: value
                    };
                }

                return {
                    samples: samples,
                    frame_time: frameTime,
                    scopes_data: scopes_data,
                    counters_data: counters_data
                };
            }

            function loadStrings(d, table){
                var file = memFileCreate(d, d.length);

                while(!memFileEof(file))
                {
                    var id    = memFileReadUInt64(file);
                    var str   = memFileReadString(file);
                    table[id] = str;
                }
            }

            function loadGameObjects(d) {
                var file = memFileCreate(d, d.length);

                while(!memFileEof(file))
                {
                    var name        = memFileReadString(file);
                    var resource    = memFileReadString(file);
                    var type        = memFileReadString(file);
                    var index       = memFileReadUInt32(file);
                    var parent      = memFileReadUInt32(file);

                    var res = gameObjectCreate(name, resource, type, index, parent);
                    framesGameObjects.push(res);
                }
            }

            function loadResources(d) {
                var file = memFileCreate(d, d.length);

                while(!memFileEof(file))
                {
                    var resourceName        = memFileReadString(file);
                    var resourceType        = memFileReadString(file);
                    var resourceSize        = memFileReadUInt32(file);
                    var resourceSizeOnDisc  = memFileReadUInt32(file);
                    var resourceRefCount    = memFileReadUInt32(file);

                    var res = resourceCreate(resourceName, resourceType, resourceSize, resourceSizeOnDisc, resourceRefCount);
                    framesResources.push(res);
                }
            }

            function handlerGameObjects(evtXHR){
                if (requestGameObjects.readyState == 4) {
                    if (requestGameObjects.status == 200) {
                        var d = requestGameObjects.responseText;
                        var type = d.substring(2, 6);
                        if (type == "GOBJ") {
                            console.log("Capturing gameobjects...");
                            loadGameObjects(d.substring(6)); // skip the size (uint16) of the pascal string
                            captureGameObjectsDone();
                        }
                        else {
                            alert("Unknown chunk type: " + type);
                        }
                    }
                    else {
                        alert("Failed to load data");
                    }
                }
            }

            function handlerResources(evtXHR){
                if (requestResources.readyState == 4) {
                    if (requestResources.status == 200) {
                        var d = requestResources.responseText;
                        var type = d.substring(2, 6); // skip the size (uint16) of the pascal string (uint16)
                        if (type == "RESS") {
                            console.log("Capturing resources...");
                            loadResources(d.substring(6));
                            captureResourcesDone();
                        }
                        else {
                            alert("Unknown chunk type: " + type);
                        }
                    }
                    else {
                        alert("Failed to load data");
                    }
                }
            }

            function expandRecursive(node) {
                for (var i = 0; i < node.children.length; i++) {
                    var child = node.children[i];
                    expandRecursive(child);
                    child.rowElement.classList.remove('collapsed');
                    if (child.expandElem != undefined) {
                        child.expandElem.checked = true;
                    }
                }
            }

            function collapseRecursive(node) {
                for (var i = 0; i < node.children.length; i++) {
                    var child = node.children[i];
                    collapseRecursive(child);
                    child.rowElement.classList.add('collapsed');
                    if (child.expandElem != undefined) {
                        child.expandElem.checked = false;
                    }
                }
            }

            function toggleExpand(event)
            {
                // Get row
                var row = event.srcElement.parentElement.parentElement;

                // Get node obj from row
                var nodeId = row.getAttribute("data-node-id");
                var node = goList[nodeId];

                var expanded = event.srcElement.checked;

                // Toggle class on child elems
                for (var i = 0; i < node.children.length; i++) {
                    var child = node.children[i];
                    if (expanded) {
                        child.rowElement.classList.remove('collapsed');
                    } else {
                        collapseRecursive(child);
                        if (child.expandElem != undefined) {
                            child.expandElem.checked = false;
                        }
                        child.rowElement.classList.add('collapsed');
                    }
                }
            }

            function collectionTreeExpand() {
                for (var i = 0; i < goRootsList.length; i++) {
                    var node = goRootsList[i];
                    expandRecursive(node);
                    if (node.expandElem != undefined) {
                        node.expandElem.checked = true;
                    }
                }
            }

            function collectionTreeCollapse() {
                for (var i = 0; i < goRootsList.length; i++) {
                    var node = goRootsList[i];
                    collapseRecursive(node);
                    if (node.expandElem != undefined) {
                        node.expandElem.checked = false;
                    }
                }
            }

            function addCollectionView(tableElem, depth, node) {

                var rowElement = document.createElement("tr");
                rowElement.setAttribute("data-tree-level", depth);
                rowElement.setAttribute("data-node-id", node.id);
                if (node.children.length == 0) {
                    rowElement.classList = ["leaf"];
                }
                if (depth > 0) {
                    rowElement.classList.add("collapsed");
                }
                tableElem.appendChild(rowElement);
                node.rowElement = rowElement;

                // Name column
                var nameElement = document.createElement("td");
                nameElement.style.paddingLeft = depth * 12 + "px";
                rowElement.appendChild(nameElement);

                // Expand elem
                if (node.children.length > 0) {
                    var expandElem = document.createElement("input");
                    expandElem.type = "checkbox";
                    expandElem.id = "checkbox" + node.id;
                    expandElem.onchange = toggleExpand;
                    nameElement.appendChild(expandElem);
                    node.expandElem = expandElem;
                }

                // Name label element
                var nameSpanElem = document.createElement("label");
                nameSpanElem.innerText = node.name;
                nameSpanElem.htmlFor = "checkbox" + node.id;

                // Style by flags
                if(node.flags & 1<<0) {
                    nameSpanElem.classList.add("go_bone");
                }
                if(node.flags & 1<<1) {
                    nameSpanElem.classList.add("go_generated");
                }

                nameElement.appendChild(nameSpanElem);

                // Type column
                var typeElement = document.createElement("td");
                typeElement.innerText = node.type;
                rowElement.appendChild(typeElement);

                // Resource column
                var resourceElement = document.createElement("td");
                resourceElement.innerText = node.resource;
                rowElement.appendChild(resourceElement);

                for (var i = 0; i < node.children.length; i++) {
                    addCollectionView(tableElem, depth+1, node.children[i]);
                }

                goList[node.id] = node;
            }

            function rebuildGameObjectHTML(event) {
                goList = {};
                goRootsList = [];

                // Clear visual view
                var collectionTreeElem = document.getElementById("collection_table_body");
                collectionTreeElem.innerHTML = "";

                // Build collection hierarchy
                var nodes = [];
                for (var i = 0; i < framesGameObjects.length; i++) {
                    var item = framesGameObjects[i];

                    var newNode = { id: item.index, name: item.name, resource: item.resource, type: item.type, flags : item.flags, children: [] };

                    // Find parent and attach node as child
                    var parentNode = undefined;
                    if (item.parent != 0) {
                        parentNode = nodes[item.parent];
                        parentNode.children[parentNode.children.length] = newNode;
                    } else {
                        goRootsList[goRootsList.length] = newNode;
                    }

                    newNode.parent = parentNode;
                    nodes[item.index] = newNode;
                }

                // Build visual view of hierarchy
                for (var i = 0; i < goRootsList.length; i++) {
                    addCollectionView(collectionTreeElem, 0, goRootsList[i]);
                }
            }

            function captureGameObjectsDone(){
                console.log("Capturing gameobjects done.");
                rebuildGameObjectHTML();
            }

            function humanReadableSize(bytes) {
                var unit = 0;
                while (bytes >= 1024) {
                    bytes /= 1024;
                    unit++;
                }
                return (unit ? bytes.toFixed(1) + ' ' : bytes) + ' KMGTPEZY'[unit] + 'B';
            }

            function rebuildResourceItems(event) {
                // Build resource table
                resourceItems = [];

                resourceSizeTotal = 0;
                resourceSizeOnDiscTotal = 0;
                for (var i = 0; i < framesResources.length; i++) {
                    var res = framesResources[i];
                    resourceItems[i] = {    name: res.name,
                                            type: res.type,
                                            size: res.size,
                                            sizeOnDisc: res.sizeOnDisc,
                                            humanReadableSize: humanReadableSize(res.size),
                                            humanReadableSizeOnDisc: humanReadableSize(res.sizeOnDisc),
                                            referenceCount: res.refCount };

                    resourceSizeTotal += res.size;
                    resourceSizeOnDiscTotal += res.sizeOnDisc;
                }
            }

            var setResourceSortPredicate = function(object) {
                var typeToName = {
                    resources_resource_label: "Resource",
                    resources_size_label: "Size",
                    resources_size_on_disc_label: "Size on Disc",
                    resources_type_label: "Type",
                    resources_refcount_label: "RefCount"
                };
                var typeToExtra = {
                    resources_resource_label: "",
                    resources_size_label: '<div id="resources_size_total_label"></div>',
                    resources_size_on_disc_label: '<div id="resources_size_on_disc_total_label"></div>',
                    resources_type_label: "",
                    resources_refcount_label: ""
                };

                if(sortResourcesSettings.id == object.id) {
                    sortResourcesSettings.descending = !sortResourcesSettings.descending;
                } else {
                    var e = document.getElementById(sortResourcesSettings.id);
                    e.innerHTML = '<div class="resource-table-sort-none">&#x25BC</div><div class="resource-table-label">' + typeToName[sortResourcesSettings.id] + '</div>' + typeToExtra[sortResourcesSettings.id];
                    sortResourcesSettings.id = object.id;
                    sortResourcesSettings.descending = true;
                }

                object.innerHTML = '<div class="resource-table-sort">' + (sortResourcesSettings.descending ? '&#x25BC' : '&#x25B2') + '</div><div class="resource-table-label">' + typeToName[sortResourcesSettings.id]  + '</div>' + typeToExtra[sortResourcesSettings.id];
                rebuildResourceView();
            }

            function rebuildResourceView() {

                // Sort items on predicate
                var typeToKey = {
                    resources_resource_label: "name",
                    resources_size_label: "size",
                    resources_size_on_disc_label: "sizeOnDisc",
                    resources_type_label: "type",
                    resources_refcount_label: "referenceCount"
                };
                var typeKey = typeToKey[sortResourcesSettings.id];
                switch( typeKey ) {
                    case "name":
                    case "type":
                        resourceItems.sort( sortResourcesSettings.descending ? function(a, b) { return a[typeKey] < b[typeKey] ? -1 : (a[typeKey] > b[typeKey] ? 1 : 0) }  :  function(a, b) { return a[typeKey] < b[typeKey] ? 1 : (a[typeKey] > b[typeKey] ? -1 : 0) } );
                        break;

                    default:
                        resourceItems.sort( sortResourcesSettings.descending ? function(a, b) { return b[typeKey] - a[typeKey]; } : function(b, a) { return b[typeKey] - a[typeKey]; } );
                        break;
                }

                // Clear visual view
                var resourceTableElem = document.getElementById("resource_table_body");
                resourceTableElem.innerHTML = "";

                // Build visual view of resources
                for (var i = 0; i < resourceItems.length; i++) {
                    var rowElem = document.createElement("tr");
                    resourceTableElem.appendChild(rowElem);

                    var nameElem = document.createElement("td");
                    nameElem.innerText = resourceItems[i].name;
                    rowElem.appendChild(nameElem);

                    var sizeElem = document.createElement("td");
                    sizeElem.classList = ["human-readable"];
                    sizeElem.innerHTML = '<span title="' + resourceItems[i].size + ' bytes">' + resourceItems[i].humanReadableSize + "</span>";
                    rowElem.appendChild(sizeElem);

                    var sizeOnDiscElem = document.createElement("td");
                    sizeOnDiscElem.classList = ["human-readable"];
                    sizeOnDiscElem.innerHTML = '<span title="' + resourceItems[i].sizeOnDisc + ' bytes">' + resourceItems[i].humanReadableSizeOnDisc + "</span>";
                    rowElem.appendChild(sizeOnDiscElem);

                    var typeElem = document.createElement("td");
                    typeElem.innerText = resourceItems[i].type;
                    rowElem.appendChild(typeElem);

                    var refcountElem = document.createElement("td");
                    refcountElem.innerText = resourceItems[i].referenceCount;
                    rowElem.appendChild(refcountElem);
                }

                var e = document.getElementById("resources_size_total_label");
                e.innerHTML = " (" + humanReadableSize(resourceSizeTotal) + ")";
                var e = document.getElementById("resources_size_on_disc_total_label");
                e.innerHTML = " (" + humanReadableSize(resourceSizeOnDiscTotal) + ")";
            }

            function rebuildResourceHTML(event) {
                rebuildResourceItems(event);
                rebuildResourceView();
            }

            function captureResourcesDone(){
                console.log("Capturing resources done.");
                rebuildResourceHTML();
            }

            function init(){
                switchTab();
            }
        </script>
    </head>
    <body onload="init();">
        <div style="margin-bottom: 8px;">
            <input type="button" value="Capture" onclick="capture();">
            Profiler: 
            <input type="radio" onchange="switchTab()" value="tab_resources" name="tab_selection" id="tab_selection_resources" checked>
            <label for="tab_selection_resources">Resources</label>
        </div>
        <div id="tab_resources" style="display: none;">
            <input type="button" value="Expand All" onclick="collectionTreeExpand();">
            <input type="button" value="Collapse All" onclick="collectionTreeCollapse();">
            <div id="res_tables">
                <table id="collection_table">
                    <thead>
                        <tr>
                            <th>Collection</th>
                            <th>Type</th>
                            <th>Resource</th>
                        </tr>
                    </thead>
                    <tbody id="collection_table_body">
                    </tbody>
                </table>
                <table id="resource_table">
                    <thead>
                        <tr>
                            <th onClick="setResourceSortPredicate(this);" id="resources_resource_label"><div class="resource-table-sort-none">&#x25BC;</div><div class="resource-table-label">Resource</div></th>
                            <th onClick="setResourceSortPredicate(this);" id="resources_size_label"><div class="resource-table-sort">&#x25BC;</div><div class="resource-table-label">Size</div><div id="resources_size_total_label"></div></th>
                            <th onClick="setResourceSortPredicate(this);" id="resources_size_on_disc_label"><div class="resource-table-sort-none">&#x25BC;</div><div class="resource-table-label">Size On Disc</div><div id="resources_size_on_disc_total_label"></div></th>
                            <th onClick="setResourceSortPredicate(this);" id="resources_type_label"><div class="resource-table-sort-none">&#x25BC;</div><div class="resource-table-label">Type</div></th>
                            <th onClick="setResourceSortPredicate(this);" id="resources_refcount_label"><div class="resource-table-sort-none">&#x25BC;</div><div class="resource-table-label">RefCount</div></th>
                        </tr>
                    </thead>
                    <tbody id="resource_table_body">
                    </tbody>
                </table>
            </div>
        </div>
    </body>
</html>
